#!/usr/bin/env python

"""
=============
PyHPFrameWork
=============

This is the PyHP FrameWork, the main WSGI application that wraps everything PyHP-related.
"""

import sys, os, cgi, traceback

from pyhp_exec import PyHPExec
from pyhp import PyHP

class PyHPFrameWork ( object ):
	"""
	PyHPFrameWork
	-------------

	The PyHPFrameWork constructor accepts the following parameters:

		- ``app``: 	the WSGI app to wrap
		- ``session_load``: ths session_load callback (default: ``None``)
		- ``session_save``: ths session_save callback (default: ``None``)
		- ``session_delete``: ths session_delete callback (default: ``None``)

	If you do not provide ``session_load`` and ``session_save`` the default ones will be used
	in the ``Session`` class. Default methods will write to a temporary directory.
	"""
	def __init__ ( self, app, session_load = None, session_save = None, session_delete = None ):

		self.pyhp = PyHP ( session_load = session_load, session_save = session_save, session_delete = session_delete )
		self.pyhp_exec = PyHPExec ( self.pyhp )

		self.app = app

	def _handle_file ( self, res, environ, start_response ):
		sys.stderr.write ( "********** TURBO REPLY!!\n" )
		fname = os.path.join ( environ.get ( 'pyhp_target_path', '' ), environ [ 'pyhp_target_name' ] )

		import mimetypes

		headers = [ ( 'Content-Type', mimetypes.guess_type ( environ [ 'pyhp_target_name' ] ) [ 0 ] ), ]

		start_response ( open ( fname ).read (), headers )
		return ''
		#return open ( fname ).read () 

	def __call__ ( self, environ, start_response ):
		if environ.get ( "SCRIPT_FILENAME" ):
			os.chdir ( os.path.dirname ( environ [ 'SCRIPT_FILENAME' ] ) )
			sys.path.insert ( 0, "." )
		
		if "PATH" not in environ:
			# FIXME: resolve path from SCRIPT_FILNAME (or such) when on MOD_WSGI
			environ [ 'PATH' ] = os.path.dirname ( environ [ 'PATH_INFO' ] )

		if "DOCUMENT_ROOT" not in environ:
			environ [ 'DOCUMENT_ROOT' ] = os.getcwd ()


		self._set_env_vars ( environ )
		self._parse_path_info ( environ )

		"""
		If it is a simple file, we don't have to do anything
		and can return as soon as possible
		"""
		#if environ [ 'pyhp_target_type' ] == 'file':
			#return self._handle_file ( self.app, environ, start_response )

		self.pyhp.init ( environ )

		self._parse_get ( environ )
		self._parse_post ( environ )

		environ [ 'pyhp' ] = self.pyhp
		environ [ 'pyhp-exec' ] = self.pyhp_exec


		res = []
		try:
			res = self.app ( environ, self._my_start_response )
		except:
			self.pyhp.log.error ( "Exception" )
			self.pyhp.return_code = "500 Internal Server Error"
			exc, txt, tb = sys.exc_info ()
			self.pyhp.log.critical ( txt )

			msg = ''.join ( traceback.format_tb ( tb ) )
			self.pyhp.log.critical ( msg )
		finally:
			start_response ( self.pyhp.return_code, self.pyhp.get_headers () )
			self.pyhp.session.save ()

		return res

	def _safe_get_env ( self, environ, keys, defval = "" ):
		if not isinstance ( keys, list ): keys = [ keys ]

		for key in keys:
			try:
				res = environ [ key ]
			except:
				res = ""
			
			res = res.strip ()
			if res: return res

		return defval

	def _set_env_vars ( self, environ ):
		sn = self._safe_get_env ( environ, "SCRIPT_NAME" )

		if not sn:
			environ [ 'SCRIPT_NAME' ] = "/" + self._safe_get_env ( environ, [ 'SCRIPT_FILENAME', 'pyhp_target_name' ] )
			environ [ 'SCRIPT_FILENAME' ] = os.path.join ( environ [ 'PWD' ], environ [ 'SCRIPT_NAME' ] )

		uri = self._safe_get_env ( environ, "REQUEST_URI" )
		if not uri:
			qs = self._safe_get_env ( environ, "QUERY_STRING" )

			environ [ 'REQUEST_URI' ] = environ [ 'SCRIPT_NAME' ]
			if qs:
				environ [ 'REQUEST_URI' ] = environ [ 'REQUEST_URI' ] + "?" + qs

	def _my_start_response ( self, ret_code, headers ):
		if ret_code:
			self.pyhp.return_code = ret_code

		if headers:
			for h,v in headers:
				self.pyhp._headers [ h ] = v

	def _parse_path_info ( self, environ ):
		environ [ 'pyhp_target_type' ] = 'unkn'
		environ [ 'pyhp_target_name' ] = '/'
		environ [ 'pyhp_target_path' ] = ''

		path = environ [ 'PATH_INFO' ]
		if not path:
			path = environ [ 'SCRIPT_NAME' ]

		# 0. Se il path e' solo "/" non faccio nulla
		#    (devo invocare l'handler di default)
		if path == '/': return

		path = self._resolve_fname ( environ, path )

		# 1. Taglio il path su "/" eliminando i campi vuoti
		path = [ x for x in path.split ( "/" ) if x ]

		# 1.1 L'ultimo campo dovrebbe potrebbe essere il nome di un file
		#     ne estraggo l'estensione
		if len ( path ):
			basename, ext = os.path.splitext ( path [ -1 ] )
			filename = os.path.join ( *path )
		else:
			basename, ext = ( "", "" )
			filename = ""

		rpath = path [ : -1 ]

		environ [ 'pyhp_target_name' ] = filename
		if ext:
			environ [ 'pyhp_target_ext' ] = ext [ 1 : ]
		else:
			environ [ 'pyhp_target_ext' ] = ''


		sys.stderr.write ( "EXT: %s - filename: %s\n" % ( ext, filename ) )

		# Cerco di capire se e' una directory (vera)
		if os.path.isdir ( filename ):
			environ [ 'pyhp_target_type' ] = 'dir'
			environ [ 'pyhp_target_path' ] = os.path.join ( *path )

		# Cerco di capire se e' un file (vero)
		elif os.path.isfile ( filename ):
			if environ [ 'pyhp_target_ext' ] == 'pyhp':
				environ [ 'pyhp_target_type' ] = 'pyhp'
			else:
				environ [ 'pyhp_target_type' ] = 'file'
			

			if rpath:
				environ [ 'pyhp_target_path' ] = os.path.join ( *rpath )
				environ [ 'pyhp_target_name' ] = basename + ext

		# Se arrivo qui, non e' ne' un file ne' una vera dir.
		# Potrebbe essere una URL "finta", ma non so come gestirla
		else:
			environ [ 'pyhp_target_type' ] = 'unkn'

	def _resolve_fname ( self, environ, filename ):
		doc_root = environ [ 'DOCUMENT_ROOT' ]
		script_filename = environ [ 'SCRIPT_FILENAME' ]

		script_fname = script_filename.replace ( doc_root, "" )
		rep = "/".join ( [ x for x in script_fname.split ( "/" ) if x ] [ : -1 ] )

		dest = filename.replace ( rep, "" ).replace ( "/", "", 1 )

		#sys.stderr.write ( "DOC: %s - SCRIPT: %s - DEST: %s - rep: %s - filename: %s - dest: %s\n" % ( doc_root, script_filename, script_fname, rep, filename, dest ) )

		# Set pyhp_web_dir to the default web location of current module
		# By default, "rep" should be "" if the wsgi has been run from DOCUMENT_ROOT
		# or contain a path if it is a sub directory module
		if rep: rep = "/" + rep
		environ [ 'pyhp_web_dir' ] = rep
		
		return dest
			
	def _parse_get ( self, environ ):
		self.pyhp.get = self._args_reduce ( self._parse_req ( environ [ 'QUERY_STRING' ] ) )

	def _args_reduce ( self, dct ):
		res = {}
		for k,v in dct.iteritems ():
			if isinstance ( v, list ) and len ( v ) == 1:
				res [ k ] = v [ 0 ]
			else:
				res [ k ] = v

		return res

	def _parse_post ( self, environ ):
		# Hide query string to FieldStorage
		qs = environ [ 'QUERY_STRING' ]
		environ [ 'QUERY_STRING' ] = ''

		self.pyhp.post = {}

		fields = cgi.FieldStorage ( fp = environ [ 'wsgi.input' ], environ = environ )
		res = {}
		for k in fields.keys ():
			fs = fields [ k ]

			#self.pyhp.log.debug ( "-- FS: %s - %s - %s\n" % ( k, hasattr ( fs, "filename" ), fs ) )

			if not isinstance ( fs, list ) and fs.filename:
				dct = {}
				for k in ( "name", "filename", "file", "type", "type_options", "disposition", "disposition_options", "headers" ):
					dct [ k ] = getattr ( fs, k )

				self.pyhp.uploads [ k ] = dct
			else:
				if isinstance ( fs.value, list ) and len ( fs.value ):
					self.pyhp.post [ k ] = fs.value [ len ( fs.value ) -1 ]
				else:
					self.pyhp.post [ k ] = fs.value

		environ [ 'QUERY_STRING' ] = qs

	def _parse_req ( self, string ):
		import urllib

		dct = {}
		if not string:
			return dct

		for pair in string.split ( "&" ):
			if not len ( pair ): continue

			if pair.find ( "=" ) != -1:
				name, val = pair.split ( "=", 1 )
				name = urllib.unquote ( name )
				val  = urllib.unquote ( val )

				lst = dct.setdefault ( name, [] )
				lst.append ( val )

		return dct
